﻿//-----------------------------------------------------------------------
// <copyright file="AddElement.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Steve Overall</author>
// <email>v-stevov</email>
// <date>2005-04-15</date>
// <summary>Adds a new element to an XML file.</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.XmlFile
{
	#region Using directives

	using System;
	using System.Collections.Generic;
	using System.Text;
	using System.Xml;

	using Microsoft.Build.Framework;
	using Microsoft.Build.Tasks;
	using Microsoft.Build.Utilities;

	using Microsoft.Sdc.Tasks;

	#endregion

	#region Class Comments
	/// <summary>
	/// Adds a new child element to any elements within the specified XML file
	/// that match the specified XPath expression.  Optionally, it will also 
	/// add a specified Key Attribute/Value pair to the new element so that subsequent
	/// tasks can identify the new element.
	/// </summary>
	/// <remarks>
	/// <code>
	/// <![CDATA[<XmlFile.AddElement Path="path" XPath="xpath" Name="name" Prefix="prefix" Value="value" KeyAttributeName="keyAttributeName" KeyAttributeValue="keyAttributeValue" Prepend="prepend" Namespaces="namespaces" IgnoreNoMatchFailure="ignoreNoMatchFalure" Force="force"/>]]>
	/// </code>
	/// <para>where:</para>
	/// <para><i>Path (Required)</i></para>
	/// <para>The path of the XML file to modify.</para>
	/// <para><i>XPath (Required)</i></para>
	/// <para>The XPath expression to evaluate to find the parent element(s) to add the new element as a child.</para>
	/// <para><i>Name (Required)</i></para>
	/// <para>The local name of the new element.</para>
	/// <para><i>Value</i></para>
	/// <para>Optional value to give the new element.</para>
	/// <para><i>Prefix</i></para>
	/// <para>Optional prefix for the namespace that this new element is to be a member of.</para>
	/// <para><i>KeyAttributeName</i></para>
	/// <para>The name of the optional attribute to create in the new element to aid its location by subsequent tasks.</para>
	/// <para><i>KeyAttributeValue</i></para>
	/// <para>The value of the optional attribute to create in the new element to aid its location by subsequent tasks.</para>
	/// <para><i>Prepend</i></para>
	/// <para>If true the new element is added to the beginning of the child elements of those elements matching the XPath expression.  
	/// If false the new element is added to the end.  Defaults to false.</para>
	/// <para><i>Namespaces</i></para>
	/// <para>
	/// Optional TaskItems array detailing each of the namespace declarations required to read/maintain the target document.
	/// Each TaskItem should have 2 elements &lt;Prefix&gt; and &lt;Uri&gt;.
	/// </para>
	/// <para><i>IgnoreNoMatchFailure</i></para>
	/// <para>If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.</para>
	/// <para><i>Force</i></para>
	/// <para>If set to true, this will force the file to be updated even if it is Read Only.  Defaults to false.</para>
	/// </remarks>
	/// <example>
	/// <code><![CDATA[
	/// <Project>
	///     <Target Name="Test" >
	///         <XmlFile.AddElement 
	///				Path="c:\app.config"
	///				XPath="/configuration/appSettings"
	///				Name="add"
	///				Prefix=""
	///				Value="element value"
	///				KeyAttributeName="key"
	///				KeyAttributeValue="instrumentationConfigFile"
	///				Prepend="true"
	///				Namespaces="@(ConfigNamespaces)"
	///				IgnoreNoMatchFailure="true"
	///				Force="true"
	///			/>
	///     </Target>
	/// </Project>
	/// ]]></code>    
	/// </example>
	#endregion
	public class AddElement : BaseXmlFileTask
	{
		#region private declarations
		private string			_name					= String.Empty;
		private string			_value					= String.Empty;
		private string			_keyAttributeName		= String.Empty;
		private string			_keyAttributeValue		= String.Empty;
		private string			_prefix					= String.Empty;
		private bool			_prepend				= false;
		private bool			_ignoreNoMatchFailure	= false;
		private bool			_force					= false;
		#endregion

		#region constructors
		/// <summary>
		/// Create a new AddElement.
		/// </summary>
		public AddElement() : base()
		{
		}
		#endregion

		#region override of InternalExecute
		/// <summary>
		/// Carry out this task's action.
		/// </summary>
		protected override void InternalExecute()
		{
			//	Load the xml file.
			XmlDocument xmlDoc = this.OpenXmlFile( this.Path );
			XmlNamespaceManager nsMgr = this.GetNamespaceManagerForDoc( xmlDoc, this.Namespaces );

			//	Run the XPath Expression against the xml file.
			//	Get the list of matching parentElements.
			XmlNodeList parentElements = this.SelectXmlNodesOfType( xmlDoc, this.XPath, XmlNodeType.Element, nsMgr );
			if ( parentElements != null && parentElements.Count > 0 )
			{
				foreach ( XmlNode element in parentElements )
				{
					XmlNode newChild = null;

					//	Create the new Child element.  Add it to the beginning or end
					//	of the childNodes depending on the value of the Prefix property.
					if ( _prepend )
						newChild = element.PrependChild( xmlDoc.CreateElement( _name, nsMgr.LookupNamespace( _prefix ) ) );
					else
						newChild = element.AppendChild( xmlDoc.CreateElement( _name, nsMgr.LookupNamespace( _prefix ) ) );

					//	If we have KeyAttributeName/Value details add that.
					if ( _keyAttributeName.Length > 0 )
					{
						XmlNode keyAttrib = newChild.Attributes[_keyAttributeName];
						if ( keyAttrib == null )
						{
							keyAttrib = newChild.Attributes.Append( xmlDoc.CreateAttribute( _keyAttributeName ) );
						}
						keyAttrib.Value = _keyAttributeValue;
					}

					//	If we have a value for the new Element add that.
					if ( _value.Length > 0 )
					{
						newChild.InnerText = _value;
					}
				}

				//	Save the Xml Doc back to disc.
				this.SaveXmlFile( xmlDoc, this.Path, _force );

				Log.LogMessageFromResources( "XmlFile.AddElement.Success", this.FileName, _name, parentElements.Count, this.XPath );
			}
			else
			{
				if (_ignoreNoMatchFailure)
					Log.LogWarningFromResources( "XmlFile.NoMatch.Warning", this.FileName, this.XPath );
				else
					throw new TaskException( Log.FormatResourceString( "XmlFile.NoMatch.Exception", this.XPath ) );
			}
		}
		#endregion

		#region public Properties
		/// <summary>
		/// The name of the new element to append to each element matching the XPath Expression.
		/// </summary>
		/// <value>The name of the new element to append to each element matching the XPath Expression.</value>
		[Required]
		public string Name
		{
			get { return _name; }
			set { _name = ( value != null ? value : String.Empty ); }
		}

		/// <summary>
		/// Namespace Prefix to use for the new element.
		/// </summary>
		/// <value>Namespace Prefix to use for the new element.</value>
		public string Prefix
		{
			get { return _prefix; }
			set { _prefix = value; }
		}

		/// <summary>
		/// The optional value to assign to the new element.
		/// </summary>
		/// <value>The optional value to assign to the new element.</value>
		public string Value
		{
			get { return _value; }
			set { _value = (value != null ? value : String.Empty); }
		}

		/// <summary>
		/// The optional attribute to add to the new element.  You can use this and
		/// the related <see cref="KeyAttributeValue"/> to provide a unique key to 
		/// be able to locate the new element from a group of similarly named parentElements
		/// once it has been added.
		/// </summary>
		/// <value>The optional attribute to add to the new element.</value>
		public string KeyAttributeName
		{
			get { return _keyAttributeName; }
			set { _keyAttributeName = ( value != null ? value : string.Empty ); }
		}

		/// <summary>
		/// The optional attribute value to add to the new element.  You can use this and
		/// the related <see cref="KeyAttributeName"/> to provide a unique key to 
		/// be able to locate the new element from a group of similarly named parentElements
		/// once it has been added.
		/// </summary>
		/// <value>The optional attribute value to add to the new element.</value>
		public string KeyAttributeValue
		{
			get { return _keyAttributeValue; }
			set { _keyAttributeValue = ( value != null ? value : string.Empty ); }
		}

		/// <summary>
		/// If set to true, the new element will be added as the first child of any matching parent elements.  Otherwise it will be set as the last child.
		/// </summary>
		/// <value>If set to true, the new element will be added as the first child of any matching parent elements.  Otherwise it will be set as the last child.</value>
		public bool Prepend
		{
			get { return _prepend; }
			set { _prepend = value; }
		}

		/// <summary>
		/// If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.
		/// </summary>
		/// <value>If set to true, no Exception will be thrown if no matches made for the supplied XPath.  Defaults to false.</value>
		public bool IgnoreNoMatchFailure
		{
			get { return _ignoreNoMatchFailure; }
			set { _ignoreNoMatchFailure = value; }
		}

		/// <summary>
		/// If set to true, this will modify the xml file even if it is read-only.  Defaults to false.
		/// </summary>
		/// <value>If set to true, this will modify the xml file even if it is read-only.  Defaults to false.</value>
		public bool Force
		{
			get { return _force; }
			set { _force = value; }
		}
		#endregion
	}
}
